package com.gitee.qdbp.general.thirdpart.biz.alipay.service;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.result.ResultCode;
import com.gitee.qdbp.general.thirdpart.api.alipay.model.AlipayPayee;
import com.gitee.qdbp.general.thirdpart.api.alipay.model.AlipayPrepayParams;
import com.gitee.qdbp.general.thirdpart.api.alipay.service.IAlipayTradeService;
import com.gitee.qdbp.general.thirdpart.api.trade.model.BasePayParams;
import com.gitee.qdbp.general.thirdpart.api.trade.model.OrderData;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PaidNotify;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PaidResponse;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PrepayData;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PrepayResponse;
import com.gitee.qdbp.general.thirdpart.biz.PayUtils;
import com.gitee.qdbp.general.thirdpart.biz.alipay.model.AlipayConfig;
import com.gitee.qdbp.general.thirdpart.biz.trade.common.BaseTradeService;
import com.gitee.qdbp.general.thirdpart.enums.TradeState;
import com.gitee.qdbp.general.thirdpart.error.PaymentErrorCode;
import com.gitee.qdbp.general.trade.enums.PayChannel;
import com.gitee.qdbp.general.trade.enums.PaySource;
import com.gitee.qdbp.tools.utils.DateTools;
import com.gitee.qdbp.tools.utils.JsonTools;
import com.gitee.qdbp.tools.utils.RandomTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 支付宝交易接口<br>
 * 同一订单号在支付成功前可多次发起付款<br>
 * <pre>
 * JAR依赖: https://docs.open.alipay.com/54/103419
 * &lt;dependency&gt;
 *     &lt;groupId&gt;com.alipay.sdk&lt;/groupId&gt;
 *     &lt;artifactId&gt;alipay-sdk-java&lt;/artifactId&gt;
 *     &lt;version&gt;3.3.49.ALL&lt;/version&gt;
 * &lt;/dependency&gt;
 * </pre>
 *
 * @author zhaohuihua
 * @version 171018
 */
public class AlipayTradeService extends BaseTradeService implements IAlipayTradeService {

    @Autowired
    protected AlipayConfig config;

    public AlipayTradeService() {
        super("AlipayTrade");
    }

    /**
     * 电脑网站支付<br>
     * https://docs.open.alipay.com/270<br>
     * https://docs.open.alipay.com/270/alipay.trade.page.pay
     */
    @Override
    public String createPagePayForm(AlipayPrepayParams params) throws ServiceException {
        params.setPayChannel(PayChannel.ALIPAY);
        if (VerifyTools.isBlank(params.getPaySource())) {
            log.trace("Failed to createPrepay. PaySource is null");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (params.getPaySource() == PaySource.WX) {
            log.trace("Failed to createPrepay. PayChannel=ALIPAY then PaySource don't support WX.");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        if (log.isInfoEnabled()) {
            String test = config.isPaymentTest() ? "Test" : "";
            log.info("[AlipayTrade]{}CreatePagePayFormParams: {}", test, JsonTools.newlineLogs(params));
        }

        if (VerifyTools.isBlank(params.getNotifyUrl())) {
            params.setNotifyUrl(config.getMerchantNotifyUrl());
        }

        // 检查订单, 检查支付状态, ...
        PrepayData prepayData = this.doPrepayCheckOrder(params);

        // 检查库存, ...
        this.doPrepayCheckBusiness(prepayData);

        // 查询收款人信息
        AlipayPayee payee = this.doGetAlipayPayee(prepayData, params.getPaySource(), getDefaultAlipayPayee(prepayData));

        // 生成支付单号
        String newPaymentId = this.doGeneratePaymentId(prepayData);

        prepayData.setPayChannel(params.getPayChannel());
        prepayData.setPaySource(params.getPaySource());
        prepayData.setOrderType(params.getOrderType());
        prepayData.setPayeeAccount(payee.getAppId());
        prepayData.setPaymentId(newPaymentId);

        if (config.isPaymentTest()) { // 测试模式
            String form = this.doHandleTestPay(params, prepayData, payee);
            if (log.isInfoEnabled()) {
                String m = "[AlipayTrade]TestPaySuccess. {}";
                log.info(m, JsonTools.newlineLogs(prepayData, form.toString()));
            }
            return form;
        } else {
            // 向支付宝发送创建预付单的请求
            AlipayTradePagePayResponse alipayResponse;
            try {
                alipayResponse = this.createPrepay(params, prepayData, payee);
            } catch (AlipayApiException e) {
                // createPrepay已经记录过日志了, 只是为了获取code和message才抛AlipayApiException异常
                PrepayResponse response = new PrepayResponse();
                response.put("code", e.getErrCode());
                response.put("message", e.getErrMsg());
                this.doPrepayError(prepayData, response);
                throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
            }

            PrepayResponse response = new PrepayResponse();
            response.setTradeSerialCode(alipayResponse.getTradeNo());
            response.setExtra((JSONObject) JSON.toJSON(alipayResponse));
            // 预付单创建成功, 修改付款状态为支付中, 锁定库存, ...
            this.doPrepaySuccess(prepayData, response);

            String form = alipayResponse.getBody();
            if (log.isInfoEnabled()) {
                String m = "[AlipayTrade]CreatePagePayFormSuccess. {}";
                log.info(m, JsonTools.newlineLogs(prepayData, alipayResponse, form));
            }
            return form;
        }
    }

    /** 支付成功后, 支付宝回调处理 **/
    /** {@inheritDoc} **/
    @Override
    public String notifyHandle(Map<String, String> params) {
        if (log.isInfoEnabled()) {
            log.info("[AlipayTrade]NotifyHandleParams: \n\t{}", JsonTools.toJsonString(params));
        }

        String result;
        try {
            doNotifyHandle(params);
            result = "success";
        } catch (ServiceException e) {
            result = "fail";
        } catch (Throwable e) {
            String m = "[AlipayTrade]NotifyHandleError, {}\n\t{}";
            log.error(m, e.toString(), JsonTools.toJsonString(params), e);
            result = "fail";
        }

        if (log.isInfoEnabled()) {
            log.info("[AlipayTrade]NotifyHandleDone: {}", result);
        }

        return result;
    }

    // 支付宝回调处理
    protected void doNotifyHandle(Map<String, String> params) throws ServiceException {

        PaidNotify notify = new PaidNotify();
        notify.setPayChannel(PayChannel.ALIPAY);
        this.fillPassbackParams(notify, params.get("passback_params"));

        // 查询订单信息
        OrderData order = doPaidQueryOrder(notify);
        if (order == null) {
            if (log.isTraceEnabled()) {
                String m = "[AlipayTrade]PaidQueryOrder return null, exit process!";
                log.trace(m, JsonTools.newlineLogs(notify));
            }
            return;
        }

        // 查询收款人信息
        AlipayPayee payee = doGetAlipayPayee(order, notify, this.getDefaultAlipayPayee(order));

        // 校验签名
        this.validSignature(params, payee);

        PaidResponse response = new PaidResponse();
        response.setOrderId(order.getOrderId()); // 订单编号
        response.setPaymentId(notify.getPaymentId()); // 支付单编号
        response.setTotalAmount(PayUtils.parseDouble(params.get("total_amount"))); // 订单金额(元)
        response.setReceiptAmount(PayUtils.parseDouble(params.get("receipt_amount"))); // 实收金额(元)
        response.setPayeeId(params.get("seller_id")); // 收款人编号
        response.setPayeeAccount(payee.getAppId()); // 收款人账号
        response.setPayerId(params.get("buyer_id")); // 付款人编号
        response.setPayeeAccount(params.get("buyer_logon_id")); // 付款人账号
        response.setPayTime(PayUtils.parseDate(params.get("gmt_payment"))); // 付款时间, yyyy-MM-dd HH: mm: ss
        response.setTradeState(PayUtils.parseTradeState(params.get("trade_status"))); // 交易状态
        response.setTradeSerialCode(params.get("trade_no")); // 交易流水号
        response.setExtra(PayUtils.toKeyObjectMap(params));

        // 修改付款状态为支付成功, 核减库存, ...
        this.doNotifyHandleSuccess(order, notify, response);
    }

    // 支付测试模式
    protected String doHandleTestPay(AlipayPrepayParams params, PrepayData prepayData, AlipayPayee payee)
            throws ServiceException {

        String orderId = prepayData.getOrderId();
        Date curr = new Date();
        {
            AlipayTradePagePayResponse alipayResponse = new AlipayTradePagePayResponse();
            // 商户订单号, 商户网站订单系统中唯一订单号, 必填
            alipayResponse.setOutTradeNo(orderId);
            // 付款金额, 必填
            alipayResponse.setTotalAmount(new DecimalFormat("0.00").format(prepayData.getTotalAmount()));
            alipayResponse.setBody(prepayData.getOrderContent());

            // 预付单创建成功, 修改付款状态为支付中, 锁定库存, ...
            PrepayResponse response = new PrepayResponse();
            response.setExtra((JSONObject) JSON.toJSON(alipayResponse));
            this.doPrepaySuccess(prepayData, response);
        }

        PaidNotify notify = new PaidNotify();
        notify.setOrderType(params.getOrderType());
        notify.setPayChannel(params.getPayChannel());
        notify.setPaySource(params.getPaySource());
        notify.setPaymentId(prepayData.getPaymentId());

        // 查询订单信息
        OrderData order = this.doPaidQueryOrder(notify);
        if (order != null) {
            PaidResponse response = new PaidResponse();
            response.setOrderId(orderId); // 订单编号
            response.setPaymentId(notify.getPaymentId()); // 付款单编号
            response.setTotalAmount(prepayData.getTotalAmount()); // 订单金额(元)
            response.setReceiptAmount(0D); // 实收金额(元)
            response.setPayeeId(payee.getAppId()); // 收款人编号
            response.setPayTime(curr); // 付款时间, yyyy-MM-dd HH: mm: ss
            response.setTradeState(TradeState.TRADE_SUCCESS); // 交易状态
            response.setTradeSerialCode("TEST" + RandomTools.generateNumber(8));
            // 修改付款状态, 核减库存/自动发货, ...
            this.doNotifyHandleSuccess(order, notify, response);
        }

        StringBuilder form = new StringBuilder();
        form.append("<form name=\"punchout_form\" method=\"GET\" action=\"");
        form.append(params.getReturnUrl());
        form.append("?charset=").append(config.getCharset());
        form.append("&method=alipay.trade.page.pay.return");
        form.append("&app_id=").append(payee.getAppId());
        form.append("&total_amount=").append(prepayData.getTotalAmount());
        form.append("&out_trade_no=").append(orderId);
        form.append("&timestamp=").append(DateTools.toNormativeString(curr, true));
        form.append("\">\n</form>\n");
        form.append("<script>document.forms[0].submit();</script>");

        return form.toString();
    }

    // 验证签名
    protected void validSignature(Map<String, String> params, AlipayPayee payee) throws ServiceException {
        String publicKey = payee.getAlipayPublicKey();
        String charset = config.getCharset();
        String signType = config.getSignType();
        // 调用SDK验证签名
        try {
            boolean succ = AlipaySignature.rsaCheckV1(params, publicKey, charset, signType);
            if (!succ) {
                if (log.isErrorEnabled()) {
                    String m = "[AlipayTrade]NotifySignatureCheckError. {}";
                    log.error(m, JsonTools.newlineLogs(params));
                }
                throw new ServiceException(PaymentErrorCode.PAYMENT_SIGNATURE_CHECK_FAILED);
            }
        } catch (AlipayApiException e) {
            if (log.isErrorEnabled()) {
                String m = "[AlipayTrade]NotifySignatureCheckError. msg={}. {}";
                log.error(m, e.toString(), JsonTools.newlineLogs(params));
            }
            throw new ServiceException(PaymentErrorCode.PAYMENT_SIGNATURE_CHECK_FAILED);
        }
    }

    /** 生成支付宝客户端 **/
    protected AlipayClient generateAlipayClient(PrepayData data, AlipayPayee payee) {
        String appId = payee.getAppId();
        String privateKey = payee.getMerchantPrivateKey();
        String publicKey = payee.getAlipayPublicKey();

        String gatewayUrl = config.getAlipayGatewayUrl();
        String format = config.getDataFormat();
        String charset = config.getCharset();
        String signType = config.getSignType();
        return new DefaultAlipayClient(gatewayUrl, appId, privateKey, format, charset, publicKey, signType);
    }

    // 创建预付单
    protected AlipayTradePagePayResponse createPrepay(AlipayPrepayParams params, PrepayData prepayData,
            AlipayPayee payee) throws AlipayApiException {
        // 构造业务参数
        AlipayTradePagePayModel model = new AlipayTradePagePayModel();
        model.setProductCode("FAST_INSTANT_TRADE_PAY");
        // 商户订单号, 商户网站订单系统中唯一订单号, 必填
        model.setOutTradeNo(prepayData.getOrderId());
        // 付款金额, 必填
        model.setTotalAmount(new DecimalFormat("0.00").format(prepayData.getTotalAmount()));
        model.setSubject(prepayData.getOrderTitle());
        model.setBody(prepayData.getOrderContent());
        // 超时时间, 格式: yyyy-MM-dd HH: mm: ss
        // model.setTimeoutExpress("30m"); // 30分钟
        model.setTimeExpire(DateTools.format(prepayData.getExpireTime(), "yyyy-MM-dd HH: mm: ss"));
        // 回传参数
        model.setPassbackParams(createPassbackParams(prepayData));

        String orderId = prepayData.getOrderId();
        // 构造请求参数
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
        request.setReturnUrl(params.getReturnUrl());
        request.setNotifyUrl(params.getNotifyUrl());
        request.setBizModel(model);
        if (log.isInfoEnabled()) {
            String m = "[AlipayTrade]BeforeCreatePagePayForm. orderId={}. {}";
            log.info(m, orderId, JsonTools.newlineLogs(request));
        }
        // 发起支付请求
        AlipayTradePagePayResponse response;
        try {
            AlipayClient client = generateAlipayClient(prepayData, payee);
            response = client.pageExecute(request);
        } catch (AlipayApiException e) {
            if (log.isErrorEnabled()) {
                String m = "[AlipayTrade]CreatePagePayFormError, msg={}. {}";
                log.error(m, e.getErrMsg(), JsonTools.newlineLogs(request), e);
            }
            throw e;
        }
        if (log.isInfoEnabled()) {
            String m = "[AlipayTrade]CreatePagePayFormSuccess. orderId={}. {}";
            log.info(m, orderId, JsonTools.newlineLogs(response));
        }
        return response;
    }

    /** 获取收款人信息 **/
    protected AlipayPayee getDefaultAlipayPayee(BasePayParams params) throws ServiceException {
        String appId, privateKey, publicKey;

        PaySource paySource = params.getPaySource();
        if (paySource == PaySource.PC) {
            appId = config.getPagePayAppId();
            privateKey = config.getPagePayMerchantPrivateKey();
            publicKey = config.getPagePayAlipayPublicKey();
        } else if (paySource == PaySource.H5) {
            appId = config.getWapPayAppId();
            privateKey = config.getWapPayMerchantPrivateKey();
            publicKey = config.getWapPayAlipayPublicKey();
        } else if (paySource == PaySource.APP) {
            // appId = config.getAppPayAppId();
            // privateKey = config.getAppPayMerchantPrivateKey();
            // publicKey = config.getAppPayAlipayPublicKey();
            throw new UnsupportedOperationException("UnsupportedOperation: AppPayment");
        } else {
            appId = config.getDefaultAppId();
            privateKey = config.getDefaultMerchantPrivateKey();
            publicKey = config.getDefaultAlipayPublicKey();
        }

        AlipayPayee payee = new AlipayPayee();
        payee.setAppId(appId);
        payee.setMerchantPrivateKey(privateKey);
        payee.setAlipayPublicKey(publicKey);
        return payee;
    }

    // 创建回调参数, P=付款单号, S=支付来源, T=订单类型
    protected String createPassbackParams(PrepayData data) {
        Map<String, Object> map = new HashMap<>();
        map.put("P", data.getPaymentId());
        if (VerifyTools.isNotBlank(data.getPaySource())) {
            map.put("S", data.getPaySource());
        }
        if (VerifyTools.isNotBlank(data.getOrderType())) {
            map.put("T", data.getOrderType());
        }
        return map.isEmpty() ? null : formatPassbackParams(map);
    }

    // 支付回调时解析回调参数, P=付款单号, S=支付来源, T=订单类型
    protected void fillPassbackParams(PaidNotify notify, String passbackString) {
        Map<String, String> passbackParams = parsePassbackParams(passbackString);
        if (passbackParams.containsKey("P")) {
            notify.setPaymentId(passbackParams.get("P"));
        }
        if (passbackParams.containsKey("S")) {
            notify.setPaySource(PaySource.valueOf(passbackParams.get("S")));
        }
        if (passbackParams.containsKey("T")) {
            notify.setOrderType(passbackParams.get("T"));
        }
    }

    protected String formatPassbackParams(Map<String, Object> params) {
        if (params == null) {
            return null;
        }

        // 最大长度: 512
        // 公用回传参数，如果请求时传递了该参数，则返回给商户时会回传该参数。
        // 支付宝会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝
        String string = PayUtils.mapToParams(params);
        String charset = config.getCharset();
        try {
            return URLEncoder.encode(string, config.getCharset());
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("UnsupportedEncoding: " + charset);
        }
    }

    protected Map<String, String> parsePassbackParams(String string) {
        if (VerifyTools.isBlank(string)) {
            return new HashMap<>();
        }

        String charset = config.getCharset();
        try {
            return PayUtils.paramsToMap(URLDecoder.decode(string, charset));
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("UnsupportedEncoding: " + charset);
        }
    }

}
